home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / chrome / browser.jar / content / browser / places / treeView.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  51.3 KB  |  1,417 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Mozilla History System
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  * Google Inc.
  19.  * Portions created by the Initial Developer are Copyright (C) 2005
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Brett Wilson <brettw@gmail.com> (original author)
  24.  *   Asaf Romano <mano@mozilla.com> (Javascript version)
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. PlacesTreeView.prototype = {
  41.   _makeAtom: function PTV__makeAtom(aString) {
  42.     return  Cc["@mozilla.org/atom-service;1"].
  43.             getService(Ci.nsIAtomService).
  44.             getAtom(aString);
  45.   },
  46.  
  47.   _atoms: [],
  48.   _getAtomFor: function PTV__getAtomFor(aName) {
  49.     if (!this._atoms[aName])
  50.       this._atoms[aName] = this._makeAtom(aName);
  51.  
  52.     return this._atoms[aName];
  53.   },
  54.  
  55.   _ensureValidRow: function PTV__ensureValidRow(aRow) {
  56.     if (aRow < 0 || aRow >= this._visibleElements.length)
  57.       throw Cr.NS_ERROR_INVALID_ARG;
  58.   },
  59.  
  60.   __dateService: null,
  61.   get _dateService() {
  62.     if (!this.__dateService) {
  63.       this.__dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
  64.                            getService(Ci.nsIScriptableDateFormat);
  65.     }
  66.     return this.__dateService;
  67.   },
  68.  
  69.   QueryInterface: function PTV_QueryInterface(aIID) {
  70.     if (aIID.equals(Ci.nsITreeView) ||
  71.         aIID.equals(Ci.nsINavHistoryResultViewer) ||
  72.         aIID.equals(Ci.nsINavHistoryResultTreeViewer) ||
  73.         aIID.equals(Ci.nsISupports))
  74.       return this;
  75.  
  76.     throw Cr.NS_ERROR_NO_INTERFACE;
  77.   },
  78.  
  79.   /**
  80.    * This is called when the result or tree may have changed.
  81.    * It reinitializes everything. Result and/or tree can be null
  82.    * when calling.
  83.    */
  84.   _finishInit: function PTV__finishInit() {
  85.     if (this._tree && this._result)
  86.       this.sortingChanged(this._result.sortingMode);
  87.  
  88.     var qoInt = Ci.nsINavHistoryQueryOptions;
  89.     var options = asQuery(this._result.root).queryOptions;
  90.  
  91.     // if there is no tree, BuildVisibleList will clear everything for us
  92.     this._buildVisibleList();
  93.   },
  94.  
  95.   _computeShowSessions: function PTV__computeShowSessions() {
  96.     NS_ASSERT(this._result, "Must have a result to show sessions!");
  97.     this._showSessions = false;
  98.  
  99.     var options = asQuery(this._result.root).queryOptions;
  100.     NS_ASSERT(options, "navHistoryResults must have valid options");
  101.  
  102.     if (!options.showSessions)
  103.       return; // sessions are off
  104.  
  105.     var resultType = options.resultType;
  106.     if (resultType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT &&
  107.         resultType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_FULL_VISIT)
  108.       return; // not visits
  109.  
  110.     var sortType = this._result.sortingMode;
  111.     if (sortType != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
  112.         sortType != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
  113.       return; // not date sorting
  114.  
  115.     this._showSessions = true;
  116.   },
  117.  
  118.   SESSION_STATUS_NONE: 0,
  119.   SESSION_STATUS_START: 1,
  120.   SESSION_STATUS_CONTINUE: 2,
  121.   _getRowSessionStatus: function PTV__getRowSessionStatus(aRow) {
  122.     var node = this._visibleElements[aRow].node;
  123.     if (!PlacesUtils.nodeIsVisit(node) || asVisit(node).sessionId == 0)
  124.       return this.SESSION_STATUS_NONE;
  125.  
  126.     if (aRow == 0)
  127.       return this.SESSION_STATUS_START;
  128.  
  129.     var previousNode = this._visibleElements[aRow - 1].node;
  130.     if (!PlacesUtils.nodeIsVisit(previousNode) ||
  131.         node.sessionId != asVisit(previousNode).sessionId)
  132.       return this.SESSION_STATUS_START;
  133.  
  134.     return this.SESSION_STATUS_CONTINUE;
  135.   },
  136.  
  137.   /**
  138.    * Call to completely rebuild the list of visible items. Note if there is no
  139.    * tree or root this will just clear out the list, so you can also call this
  140.    * when a tree is detached to clear the list.
  141.    */
  142.   _buildVisibleList: function PTV__buildVisibleList() {
  143.     var selection = this.selection;
  144.     if (selection)
  145.       selection.selectEventsSuppressed = true;
  146.  
  147.     if (this._result) {
  148.       // Any current visible elements need to be marked as invisible.
  149.       for (var i = 0; i < this._visibleElements.length; i++) {
  150.         this._visibleElements[i].node.viewIndex = -1;
  151.       }
  152.     }
  153.  
  154.     var rootNode = this._result.root;
  155.     if (rootNode && this._tree) {
  156.       this._computeShowSessions();
  157.  
  158.       asContainer(rootNode);
  159.       if (this._showRoot) {
  160.         // List the root node
  161.         this._visibleElements.push(
  162.           { node: this._result.root, properties: null });
  163.         this._tree.rowCountChanged(0, 1);
  164.         this._result.root.viewIndex = 0;
  165.       }
  166.       else if (!rootNode.containerOpen) {
  167.         // this triggers containerOpened which then builds the visible
  168.         // section
  169.         rootNode.containerOpen = true;
  170.       }
  171.       else
  172.         this.invalidateContainer(rootNode);
  173.     }
  174.     if (selection)
  175.       selection.selectEventsSuppressed = false;
  176.   },
  177.  
  178.   /**
  179.    * This takes a container and recursively appends visible elements to the
  180.    * given array. This is used to build the visible element list (with
  181.    * this._visibleElements passed as the array), or portions thereof (with
  182.    * a separate array that is merged with the main list later.
  183.    *
  184.    * aVisibleStartIndex is the visible index of the beginning of the 'aVisible'
  185.    * array. When aVisible is this._visibleElements, this is 0. This is non-zero
  186.    * when we are building up a sub-region for insertion. Then, this is the
  187.    * index where the new array will be inserted into this._visibleElements.
  188.    * It is used to compute each node's viewIndex.
  189.    */
  190.   _buildVisibleSection:
  191.   function PTV__buildVisibleSection(aContainer, aVisible, aToOpen, aVisibleStartIndex)
  192.   {
  193.     if (!aContainer.containerOpen)
  194.       return;  // nothing to do
  195.  
  196.     const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
  197.     const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
  198.  
  199.     var cc = aContainer.childCount;
  200.     for (var i=0; i < cc; i++) {
  201.       var curChild = aContainer.getChild(i);
  202.       var curChildType = curChild.type;
  203.  
  204.       // don't display separators when sorted
  205.       if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
  206.         if (this._result.sortingMode !=
  207.             Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
  208.           curChild.viewIndex = -1;
  209.           continue;
  210.         }
  211.       }
  212.  
  213.       // add item
  214.       curChild.viewIndex = aVisibleStartIndex + aVisible.length;
  215.       aVisible.push({ node: curChild, properties: null });
  216.  
  217.       // recursively do containers
  218.       if (!this._flatList && PlacesUtils.containerTypes.indexOf(curChildType) != -1) {
  219.         asContainer(curChild);
  220.  
  221.         var resource = this._getResourceForNode(curChild);
  222.         var isopen = resource != null &&
  223.                      PlacesUIUtils.localStore.HasAssertion(resource, openLiteral,
  224.                                                            trueLiteral, true);
  225.         if (isopen != curChild.containerOpen)
  226.           aToOpen.push(curChild);
  227.         else if (curChild.containerOpen && curChild.childCount > 0)
  228.           this._buildVisibleSection(curChild, aVisible, aToOpen, aVisibleStartIndex);
  229.       }
  230.     }
  231.   },
  232.  
  233.   /**
  234.    * This counts how many rows an item takes in the tree, that is, the
  235.    * item itself plus any nodes following it with an increased indent.
  236.    * This allows you to figure out how many rows an item (=1) or a
  237.    * container with all of its children takes.
  238.    */
  239.   _countVisibleRowsForItem: function PTV__countVisibleRowsForItem(aNode) {
  240.     if (aNode == this._result.root)
  241.       return this._visibleElements.length;
  242.  
  243.     var viewIndex = aNode.viewIndex;
  244.     NS_ASSERT(viewIndex >= 0, "Item is not visible, no rows to count");
  245.     var outerLevel = aNode.indentLevel;
  246.     for (var i = viewIndex + 1; i < this._visibleElements.length; i++) {
  247.       if (this._visibleElements[i].node.indentLevel <= outerLevel)
  248.         return i - viewIndex;
  249.     }
  250.     // this node plus its children occupy the bottom of the list
  251.     return this._visibleElements.length - viewIndex;
  252.   },
  253.  
  254.   /**
  255.    * This is called by containers when they change and we need to update
  256.    * everything about the container. We build a new visible section with
  257.    * the container as a separate object so we first know how the list
  258.    * changes. This way we only have to do one realloc/memcpy to update
  259.    * the list.
  260.    *
  261.    * We also try to be smart here about redrawing the screen.
  262.    */
  263.   _refreshVisibleSection: function PTV__refreshVisibleSection(aContainer) {
  264.     NS_ASSERT(this._result, "Need to have a result to update");
  265.     if (!this._tree)
  266.       return;
  267.  
  268.     // The root node is invisible if showRoot is not set. Otherwise aContainer
  269.     // must be visible
  270.     if (this._showRoot || aContainer != this._result.root) {
  271.       if (aContainer.viewIndex < 0 ||
  272.           aContainer.viewIndex > this._visibleElements.length)
  273.         throw "Trying to expand a node that is not visible";
  274.  
  275.       NS_ASSERT(this._visibleElements[aContainer.viewIndex].node == aContainer,
  276.                 "Visible index is out of sync!");
  277.     }
  278.  
  279.     var startReplacement = aContainer.viewIndex + 1;
  280.     var replaceCount = this._countVisibleRowsForItem(aContainer);
  281.  
  282.     // We don't replace the container item itself so we decrease the
  283.     // replaceCount by 1. We don't do so though if there is no visible item
  284.     // for the container. This happens when aContainer is the root node and
  285.     // showRoot is not set.
  286.     if (aContainer.viewIndex != -1)
  287.       replaceCount-=1;
  288.  
  289.     // Persist selection state
  290.     var previouslySelectedNodes = [];
  291.     var selection = this.selection;
  292.     var rc = selection.getRangeCount();
  293.     for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
  294.       var min = { }, max = { };
  295.       selection.getRangeAt(rangeIndex, min, max);
  296.       var lastIndex = Math.min(max.value, startReplacement + replaceCount -1);
  297.       // if this range does not overlap the replaced chunk we don't need to
  298.       // persist the selection.
  299.       if (max.value < startReplacement || min.value > lastIndex)
  300.         continue;
  301.       // if this range starts before the replaced chunk we should persist from
  302.       // startReplacement to lastIndex
  303.       var firstIndex = Math.max(min.value, startReplacement);
  304.       for (var nodeIndex = firstIndex; nodeIndex <= lastIndex; nodeIndex++)
  305.         previouslySelectedNodes.push(
  306.           { node: this._visibleElements[nodeIndex].node, oldIndex: nodeIndex });
  307.     }
  308.  
  309.     // Mark the removes as invisible
  310.     for (var i = 0; i < replaceCount; i++)
  311.       this._visibleElements[startReplacement + i].node.viewIndex = -1;
  312.  
  313.     // Building the new list will set the new elements' visible indices.
  314.     var newElements = [];
  315.     var toOpenElements = [];
  316.     this._buildVisibleSection(aContainer,
  317.                               newElements, toOpenElements, startReplacement);
  318.  
  319.     // actually update the visible list
  320.     this._visibleElements =
  321.       this._visibleElements.slice(0, startReplacement).concat(newElements)
  322.           .concat(this._visibleElements.slice(startReplacement + replaceCount,
  323.                                               this._visibleElements.length));
  324.  
  325.     // If the new area has a different size, we'll have to renumber the
  326.     // elements following the area.
  327.     if (replaceCount != newElements.length) {
  328.       for (var i = startReplacement + newElements.length;
  329.            i < this._visibleElements.length; i ++) {
  330.         this._visibleElements[i].node.viewIndex = i;
  331.       }
  332.     }
  333.  
  334.     // now update the number of elements
  335.     selection.selectEventsSuppressed = true;
  336.     this._tree.beginUpdateBatch();
  337.  
  338.     if (replaceCount)
  339.       this._tree.rowCountChanged(startReplacement, -replaceCount);
  340.     if (newElements.length)
  341.       this._tree.rowCountChanged(startReplacement, newElements.length);
  342.  
  343.     if (!this._flatList) {
  344.       // now, open any containers that were persisted
  345.       for (var i = 0; i < toOpenElements.length; i++) {
  346.         var item = toOpenElements[i];
  347.         var parent = item.parent;
  348.         // avoid recursively opening containers
  349.         while (parent) {
  350.           if (parent.uri == item.uri)
  351.             break;
  352.           parent = parent.parent;
  353.         }
  354.         // if we don't have a parent, we made it all the way to the root
  355.         // and didn't find a match, so we can open our item
  356.         if (!parent && !item.containerOpen)
  357.           item.containerOpen = true;
  358.       }
  359.     }
  360.  
  361.     this._tree.endUpdateBatch();
  362.  
  363.     // restore selection
  364.     if (previouslySelectedNodes.length > 0) {
  365.       for (var i = 0; i < previouslySelectedNodes.length; i++) {
  366.         var nodeInfo = previouslySelectedNodes[i];
  367.         var index = nodeInfo.node.viewIndex;
  368.  
  369.         // if the same node was used (happens on sorting-changes),
  370.         // just use viewIndex
  371.         if (index == -1) { // otherwise, try to find an equal node
  372.           var itemId = PlacesUtils.getConcreteItemId(nodeInfo.node);
  373.           if (itemId != 1) { // bookmark-nodes in queries case
  374.             for (var j = 0; j < newElements.length && index == -1; j++) {
  375.               if (PlacesUtils.getConcreteItemId(newElements[j]) == itemId)
  376.                 index = newElements[j].viewIndex;
  377.             }
  378.           }
  379.           else { // history nodes
  380.             var uri = nodeInfo.node.uri;
  381.             if (uri) {
  382.               for (var j = 0; j < newElements.length && index == -1; j++) {
  383.                 if (newElements[j].uri == uri)
  384.                   index = newElements[j].viewIndex;
  385.               }
  386.             }
  387.           }
  388.         }
  389.         if (index != -1)
  390.           selection.rangedSelect(index, index, true);
  391.       }
  392.  
  393.       // if only one node was previously selected and there's no selection now,
  394.       // select the node at its old-viewIndex, if any
  395.       if (previouslySelectedNodes.length == 1 &&
  396.           selection.getRangeCount() == 0 &&
  397.           this._visibleElements.length > previouslySelectedNodes[0].oldIndex) {
  398.         selection.rangedSelect(previouslySelectedNodes[0].oldIndex,
  399.                                previouslySelectedNodes[0].oldIndex, true);
  400.       }
  401.     }
  402.     selection.selectEventsSuppressed = false;
  403.   },
  404.  
  405.   _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
  406.     var timeInMilliseconds = aTime / 1000; // PRTime is in microseconds
  407.  
  408.     // Date is calculated starting from midnight, so the modulo with a day are
  409.     // milliseconds from today's midnight.
  410.     // getTimezoneOffset corrects that based on local time.
  411.     // 86400000 = 24 * 60 * 60 * 1000 = 1 day
  412.     // 60000 = 60 * 1000 = 1 minute
  413.     var dateObj = new Date();
  414.     var timeZoneOffsetInMs = dateObj.getTimezoneOffset() * 60000;
  415.     var now = dateObj.getTime() - timeZoneOffsetInMs;
  416.     var midnight = now - (now % (86400000));
  417.  
  418.     var dateFormat = timeInMilliseconds - timeZoneOffsetInMs >= midnight ?
  419.                       Ci.nsIScriptableDateFormat.dateFormatNone :
  420.                       Ci.nsIScriptableDateFormat.dateFormatShort;
  421.  
  422.     var timeObj = new Date(timeInMilliseconds);
  423.     return (this._dateService.FormatDateTime("", dateFormat,
  424.       Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
  425.       timeObj.getFullYear(), timeObj.getMonth() + 1,
  426.       timeObj.getDate(), timeObj.getHours(),
  427.       timeObj.getMinutes(), timeObj.getSeconds()));
  428.   },
  429.  
  430.   COLUMN_TYPE_UNKNOWN: 0,
  431.   COLUMN_TYPE_TITLE: 1,
  432.   COLUMN_TYPE_URI: 2,
  433.   COLUMN_TYPE_DATE: 3,
  434.   COLUMN_TYPE_VISITCOUNT: 4,
  435.   COLUMN_TYPE_KEYWORD: 5,
  436.   COLUMN_TYPE_DESCRIPTION: 6,
  437.   COLUMN_TYPE_DATEADDED: 7,
  438.   COLUMN_TYPE_LASTMODIFIED: 8,
  439.   COLUMN_TYPE_TAGS: 9,
  440.  
  441.   _getColumnType: function PTV__getColumnType(aColumn) {
  442.     var columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
  443.  
  444.     switch (columnType) {
  445.       case "title":
  446.         return this.COLUMN_TYPE_TITLE;
  447.       case "url":
  448.         return this.COLUMN_TYPE_URI;
  449.       case "date":
  450.         return this.COLUMN_TYPE_DATE;
  451.       case "visitCount":
  452.         return this.COLUMN_TYPE_VISITCOUNT;
  453.       case "keyword":
  454.         return this.COLUMN_TYPE_KEYWORD;
  455.       case "description":
  456.         return this.COLUMN_TYPE_DESCRIPTION;
  457.       case "dateAdded":
  458.         return this.COLUMN_TYPE_DATEADDED;
  459.       case "lastModified":
  460.         return this.COLUMN_TYPE_LASTMODIFIED;
  461.       case "tags":
  462.         return this.COLUMN_TYPE_TAGS;
  463.     }
  464.     return this.COLUMN_TYPE_UNKNOWN;
  465.   },
  466.  
  467.   _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) {
  468.     switch (aSortType) {
  469.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING:
  470.         return [this.COLUMN_TYPE_TITLE, false];
  471.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
  472.         return [this.COLUMN_TYPE_TITLE, true];
  473.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
  474.         return [this.COLUMN_TYPE_DATE, false];
  475.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
  476.         return [this.COLUMN_TYPE_DATE, true];
  477.       case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
  478.         return [this.COLUMN_TYPE_URI, false];
  479.       case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
  480.         return [this.COLUMN_TYPE_URI, true];
  481.       case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
  482.         return [this.COLUMN_TYPE_VISITCOUNT, false];
  483.       case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
  484.         return [this.COLUMN_TYPE_VISITCOUNT, true];
  485.       case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_ASCENDING:
  486.         return [this.COLUMN_TYPE_KEYWORD, false];
  487.       case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_DESCENDING:
  488.         return [this.COLUMN_TYPE_KEYWORD, true];
  489.       case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING:
  490.         if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
  491.           return [this.COLUMN_TYPE_DESCRIPTION, false];
  492.         break;
  493.       case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
  494.         if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
  495.           return [this.COLUMN_TYPE_DESCRIPTION, true];
  496.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
  497.         return [this.COLUMN_TYPE_DATEADDED, false];
  498.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING:
  499.         return [this.COLUMN_TYPE_DATEADDED, true];
  500.       case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING:
  501.         return [this.COLUMN_TYPE_LASTMODIFIED, false];
  502.       case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING:
  503.         return [this.COLUMN_TYPE_LASTMODIFIED, true];
  504.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING:
  505.         return [this.COLUMN_TYPE_TAGS, false];
  506.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING:
  507.         return [this.COLUMN_TYPE_TAGS, true];
  508.     }
  509.     return [this.COLUMN_TYPE_UNKNOWN, false];
  510.   },
  511.  
  512.   // nsINavHistoryResultViewer
  513.   itemInserted: function PTV_itemInserted(aParent, aItem, aNewIndex) {
  514.     if (!this._tree)
  515.       return;
  516.     if (!this._result)
  517.       throw Cr.NS_ERROR_UNEXPECTED;
  518.  
  519.     if (PlacesUtils.nodeIsSeparator(aItem) &&
  520.         this._result.sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
  521.       aItem.viewIndex = -1;
  522.       return;
  523.     }
  524.  
  525.     // update parent when inserting the first item because twisty may
  526.     // have changed
  527.     if (aParent.childCount == 1)
  528.       this.itemChanged(aParent);
  529.  
  530.     // compute the new view index of the item
  531.     var newViewIndex = -1;
  532.     if (aNewIndex == 0) {
  533.       // item is the first thing in our child list, it takes our index +1. Note
  534.       // that this computation still works if the parent is an invisible root
  535.       // node, because root_index + 1 = -1 + 1 = 0
  536.       newViewIndex = aParent.viewIndex + 1;
  537.     }
  538.     else {
  539.       // Here, we try to find the next visible element in the child list so we
  540.       // can set the new visible index to be right before that. Note that we
  541.       // have to search DOWN instead of up, because some siblings could have
  542.       // children themselves that would be in the way.
  543.       for (var i = aNewIndex + 1; i < aParent.childCount; i ++) {
  544.         var viewIndex = aParent.getChild(i).viewIndex;
  545.         if (viewIndex >= 0) {
  546.           // the view indices of subsequent children have not been shifted so
  547.           // the next item will have what should be our index
  548.           newViewIndex = viewIndex;
  549.           break;
  550.         }
  551.       }
  552.       if (newViewIndex < 0) {
  553.         // At the end of the child list without finding a visible sibling: This
  554.         // is a little harder because we don't know how many rows the last item
  555.         // in our list takes up (it could be a container with many children).
  556.         var prevChild = aParent.getChild(aNewIndex - 1);
  557.         newViewIndex = prevChild.viewIndex + this._countVisibleRowsForItem(prevChild);
  558.       }
  559.     }
  560.  
  561.     aItem.viewIndex = newViewIndex;
  562.     this._visibleElements.splice(newViewIndex, 0, 
  563.                                  { node: aItem, properties: null });
  564.     for (var i = newViewIndex + 1;
  565.          i < this._visibleElements.length; i ++) {
  566.       this._visibleElements[i].node.viewIndex = i;
  567.     }
  568.     this._tree.rowCountChanged(newViewIndex, 1);
  569.  
  570.     // Need to redraw the rows around this one because session boundaries
  571.     // may have changed. For example, if we add a page to a session, the
  572.     // previous page will need to be redrawn because its session border
  573.     // will disappear.
  574.     if (this._showSessions) {
  575.       if (newViewIndex > 0)
  576.         this._tree.invalidateRange(newViewIndex - 1, newViewIndex - 1);
  577.       if (newViewIndex < this._visibleElements.length -1)
  578.         this._tree.invalidateRange(newViewIndex + 1, newViewIndex + 1);
  579.     }
  580.  
  581.     if (PlacesUtils.nodeIsContainer(aItem) && asContainer(aItem).containerOpen)
  582.       this._refreshVisibleSection(aItem);
  583.   },
  584.  
  585.   // this is used in itemRemoved and itemMoved to fix viewIndex values
  586.   // throw if the item has an invalid viewIndex
  587.   _fixViewIndexOnRemove: function PTV_fixViewIndexOnRemove(aItem, aParent) {
  588.     var oldViewIndex = aItem.viewIndex;
  589.     // this may have been a container, in which case it has a lot of rows
  590.     var count = this._countVisibleRowsForItem(aItem);
  591.  
  592.     if (oldViewIndex > this._visibleElements.length)
  593.       throw("Trying to remove an item with an invalid viewIndex");
  594.  
  595.     this._visibleElements.splice(oldViewIndex, count);
  596.     for (var i = oldViewIndex; i < this._visibleElements.length; i++)
  597.       this._visibleElements[i].node.viewIndex = i;
  598.  
  599.     this._tree.rowCountChanged(oldViewIndex, -count);
  600.  
  601.     // redraw parent because twisty may have changed
  602.     if (!aParent.hasChildren)
  603.       this.itemChanged(aParent);
  604.  
  605.     return;
  606.   },
  607.  
  608.   /**
  609.    * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being
  610.    * removed but the node it is collapsed with is not being removed (this then
  611.    * just swap out the removee with its collapsing partner). The only time
  612.    * when we really remove things is when deleting URIs, which will apply to
  613.    * all collapsees. This function is called sometimes when resorting items.
  614.    * However, we won't do this when sorted by date because dates will never
  615.    * change for visits, and date sorting is the only time things are collapsed.
  616.    */
  617.   itemRemoved: function PTV_itemRemoved(aParent, aItem, aOldIndex) {
  618.     NS_ASSERT(this._result, "Got a notification but have no result!");
  619.     if (!this._tree)
  620.       return; // nothing to do
  621.  
  622.     var oldViewIndex = aItem.viewIndex;
  623.     if (oldViewIndex < 0)
  624.       return; // item was already invisible, nothing to do
  625.  
  626.     // if the item was exclusively selected, the node next to it will be
  627.     // selected
  628.     var selectNext = false;
  629.     var selection = this.selection;
  630.     if (selection.getRangeCount() == 1) {
  631.       var min = { }, max = { };
  632.       selection.getRangeAt(0, min, max);
  633.       if (min.value == max.value &&
  634.           this.nodeForTreeIndex(min.value) == aItem)
  635.         selectNext = true;
  636.     }
  637.  
  638.     // remove the item and fix viewIndex values
  639.     this._fixViewIndexOnRemove(aItem, aParent);
  640.  
  641.     // restore selection if the item was exclusively selected
  642.     if (!selectNext)
  643.       return;
  644.     // restore selection
  645.     if (this._visibleElements.length > oldViewIndex)
  646.       selection.rangedSelect(oldViewIndex, oldViewIndex, true);    
  647.     else if (this._visibleElements.length > 0) {
  648.       // if we removed the last child, we select the new last child if exists
  649.       selection.rangedSelect(this._visibleElements.length - 1,
  650.                              this._visibleElements.length - 1, true);
  651.     }
  652.   },
  653.  
  654.   /**
  655.    * Be careful, aOldIndex and aNewIndex specify the index in the
  656.    * corresponding parent nodes, not the visible indexes.
  657.    */
  658.   itemMoved:
  659.   function PTV_itemMoved(aItem, aOldParent, aOldIndex, aNewParent, aNewIndex) {
  660.     NS_ASSERT(this._result, "Got a notification but have no result!");
  661.     if (!this._tree)
  662.       return; // nothing to do
  663.  
  664.     var oldViewIndex = aItem.viewIndex;
  665.     if (oldViewIndex < 0)
  666.       return; // item was already invisible, nothing to do
  667.  
  668.     // this may have been a container, in which case it has a lot of rows
  669.     var count = this._countVisibleRowsForItem(aItem);
  670.  
  671.     // Persist selection state
  672.     var nodesToSelect = [];
  673.     var selection = this.selection;
  674.     var rc = selection.getRangeCount();
  675.     for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
  676.       var min = { }, max = { };
  677.       selection.getRangeAt(rangeIndex, min, max);
  678.       var lastIndex = Math.min(max.value, oldViewIndex + count -1);
  679.       if (min.value < oldViewIndex || min.value > lastIndex)
  680.         continue;
  681.  
  682.       for (var nodeIndex = min.value; nodeIndex <= lastIndex; nodeIndex++)
  683.         nodesToSelect.push(this._visibleElements[nodeIndex].node);
  684.     }
  685.     if (nodesToSelect.length > 0)
  686.       selection.selectEventsSuppressed = true;
  687.  
  688.     // remove item from the old position
  689.     this._fixViewIndexOnRemove(aItem, aOldParent);
  690.  
  691.     // insert the item into the new position
  692.     this.itemInserted(aNewParent, aItem, aNewIndex);
  693.  
  694.     // restore selection
  695.     if (nodesToSelect.length > 0) {
  696.       for (var i = 0; i < nodesToSelect.length; i++) {
  697.         var node = nodesToSelect[i];
  698.         var index = node.viewIndex;
  699.         selection.rangedSelect(index, index, true);
  700.       }
  701.       selection.selectEventsSuppressed = false;
  702.     }
  703.   },
  704.  
  705.   /**
  706.    * Be careful, the parameter 'aIndex' here specifies the index in the parent
  707.    * node of the item, not the visible index.
  708.    *
  709.    * This is called from the result when the item is replaced, but this object
  710.    * calls this function internally also when duplicate collapsing changes. In
  711.    * this case, aIndex will be 0, so we should be careful not to use the value.
  712.    */
  713.   itemReplaced:
  714.   function PTV_itemReplaced(aParent, aOldItem, aNewItem, aIndexDoNotUse) {
  715.     if (!this._tree)
  716.       return;
  717.  
  718.     var viewIndex = aOldItem.viewIndex;
  719.     aNewItem.viewIndex = viewIndex;
  720.     if (viewIndex >= 0 &&
  721.         viewIndex < this._visibleElements.length) {
  722.       this._visibleElements[viewIndex].node = aNewItem;
  723.       this._visibleElements[viewIndex].properties = null;
  724.     }
  725.     aOldItem.viewIndex = -1;
  726.     this._tree.invalidateRow(viewIndex);
  727.   },
  728.  
  729.   itemChanged: function PTV_itemChanged(aItem) {
  730.     NS_ASSERT(this._result, "Got a notification but have no result!");
  731.     var viewIndex = aItem.viewIndex;
  732.     if (this._tree && viewIndex >= 0)
  733.       this._tree.invalidateRow(viewIndex);
  734.   },
  735.  
  736.   containerOpened: function PTV_containerOpened(aItem) {
  737.     this.invalidateContainer(aItem);
  738.   },
  739.  
  740.   containerClosed: function PTV_containerClosed(aItem) {
  741.     this.invalidateContainer(aItem);
  742.   },
  743.  
  744.   invalidateContainer: function PTV_invalidateContainer(aItem) {
  745.     NS_ASSERT(this._result, "Got a notification but have no result!");
  746.     if (!this._tree)
  747.       return; // nothing to do, container is not visible
  748.     var viewIndex = aItem.viewIndex;
  749.     if (viewIndex >= this._visibleElements.length) {
  750.       // be paranoid about visible indices since others can change it
  751.       throw Cr.NS_ERROR_UNEXPECTED;
  752.     }
  753.     this._refreshVisibleSection(aItem);
  754.   },
  755.  
  756.   invalidateAll: function PTV_invalidateAll() {
  757.     NS_ASSERT(this._result, "Got message but don't have a result!");
  758.     if (!this._tree)
  759.       return;
  760.  
  761.     var oldRowCount = this._visibleElements.length;
  762.  
  763.     // update flat list to new contents
  764.     this._buildVisibleList();
  765.   },
  766.  
  767.   sortingChanged: function PTV__sortingChanged(aSortingMode) {
  768.     if (!this._tree || !this._result)
  769.       return;
  770.  
  771.     // depending on the sort mode, certain commands may be disabled
  772.     window.updateCommands("sort");
  773.  
  774.     var columns = this._tree.columns;
  775.  
  776.     // clear old sorting indicator
  777.     var sortedColumn = columns.getSortedColumn();
  778.     if (sortedColumn)
  779.       sortedColumn.element.removeAttribute("sortDirection");
  780.  
  781.     // set new sorting indicator by looking through all columns for ours
  782.     if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE)
  783.       return;
  784.     var [desiredColumn, desiredIsDescending] =
  785.       this._sortTypeToColumnType(aSortingMode);
  786.     var colCount = columns.count;
  787.     for (var i = 0; i < colCount; i ++) {
  788.       var column = columns.getColumnAt(i);
  789.       if (this._getColumnType(column) == desiredColumn) {
  790.         // found our desired one, set
  791.         if (desiredIsDescending)
  792.           column.element.setAttribute("sortDirection", "descending");
  793.         else
  794.           column.element.setAttribute("sortDirection", "ascending");
  795.         break;
  796.       }
  797.     }
  798.   },
  799.  
  800.   get result() {
  801.     return this._result;
  802.   },
  803.  
  804.   set result(val) {
  805.     // some methods (e.g. getURLsFromContainer) temporarily null out the
  806.     // viewer when they do temporary changes to the view, this does _not_
  807.     // call setResult(null), but then, we're called again with the result
  808.     // object which is already set for this viewer. At that point,
  809.     // we should do nothing.
  810.     if (this._result != val) {
  811.       this._result = val;
  812.       this._finishInit();
  813.     }
  814.     return val;
  815.   },
  816.  
  817.   nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) {
  818.     if (aIndex > this._visibleElements.length)
  819.       throw Cr.NS_ERROR_INVALID_ARG;
  820.  
  821.     return this._visibleElements[aIndex].node;
  822.   },
  823.  
  824.   treeIndexForNode: function PTV_treeNodeForIndex(aNode) {
  825.     var viewIndex = aNode.viewIndex;
  826.     if (viewIndex < 0)
  827.       return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
  828.  
  829.     NS_ASSERT(this._visibleElements[viewIndex].node == aNode,
  830.               "Node's visible index and array out of sync");
  831.     return viewIndex;
  832.   },
  833.  
  834.   _getResourceForNode: function PTV_getResourceForNode(aNode)
  835.   {
  836.     var uri = aNode.uri;
  837.     NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
  838.     return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
  839.   },
  840.  
  841.   // nsITreeView
  842.   get rowCount() {
  843.     return this._visibleElements.length;
  844.   },
  845.  
  846.   get selection() {
  847.     return this._selection;
  848.   },
  849.  
  850.   set selection(val) {
  851.     return this._selection = val;
  852.   },
  853.  
  854.   getRowProperties: function PTV_getRowProperties(aRow, aProperties) {
  855.     this._ensureValidRow(aRow);
  856.  
  857.     // Handle properties for session information.
  858.     if (!this._showSessions)
  859.       return;
  860.  
  861.     var status = this._getRowSessionStatus(aRow);
  862.     switch (status) {
  863.       case this.SESSION_STATUS_NONE:
  864.         break;
  865.       case this.SESSION_STATUS_START:
  866.         aProperties.AppendElement(this._getAtomFor("session-start"));
  867.         break;
  868.       case this.SESSION_STATUS_CONTINUE:
  869.         aProperties.AppendElement(this._getAtomFor("session-continue"));
  870.         break
  871.     }
  872.   },
  873.  
  874.   getCellProperties: function PTV_getCellProperties(aRow, aColumn, aProperties) {
  875.     this._ensureValidRow(aRow);
  876.  
  877.     // for anonid-trees, we need to add the column-type manually
  878.     var columnType = aColumn.element.getAttribute("anonid");
  879.     if (columnType)
  880.       aProperties.AppendElement(this._getAtomFor(columnType));
  881.     else
  882.       var columnType = aColumn.id;
  883.  
  884.     // Set the "ltr" property on url cells
  885.     if (columnType == "url")
  886.       aProperties.AppendElement(this._getAtomFor("ltr"));
  887.  
  888.     if (columnType != "title")
  889.       return;
  890.  
  891.     var node = this._visibleElements[aRow].node;
  892.     var properties = this._visibleElements[aRow].properties;
  893.  
  894.     if (!properties) {
  895.       properties = new Array();
  896.       var itemId = node.itemId;
  897.       var nodeType = node.type;
  898.       if (PlacesUtils.containerTypes.indexOf(nodeType) != -1) {
  899.         if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
  900.           properties.push(this._getAtomFor("query"));
  901.           if (PlacesUtils.nodeIsTagQuery(node))
  902.             properties.push(this._getAtomFor("tagContainer"));
  903.           else if (PlacesUtils.nodeIsDay(node))
  904.             properties.push(this._getAtomFor("dayContainer"));
  905.           else if (PlacesUtils.nodeIsHost(node))
  906.             properties.push(this._getAtomFor("hostContainer"));
  907.         }
  908.         else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
  909.                  nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
  910.           if (PlacesUtils.annotations.itemHasAnnotation(itemId,
  911.                                                         LMANNO_FEEDURI))
  912.             properties.push(this._getAtomFor("livemark"));
  913.         }
  914.  
  915.         if (itemId != -1) {
  916.           var oqAnno;
  917.           try {
  918.             oqAnno = PlacesUtils.annotations
  919.                                 .getItemAnnotation(itemId,
  920.                                                    ORGANIZER_QUERY_ANNO);
  921.             properties.push(this._getAtomFor("OrganizerQuery_" + oqAnno));
  922.           }
  923.           catch (ex) { /* not a special query */ }
  924.         }
  925.       }
  926.       else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
  927.         properties.push(this._getAtomFor("separator"));
  928.       else if (PlacesUtils.nodeIsURI(node)) {
  929.         properties.push(this._getAtomFor(PlacesUIUtils.guessUrlSchemeForUI(node.uri)));
  930.         if (itemId != -1) {
  931.           if (PlacesUtils.nodeIsLivemarkContainer(node.parent))
  932.             properties.push(this._getAtomFor("livemarkItem"));
  933.         }
  934.       }
  935.  
  936.       this._visibleElements[aRow].properties = properties;
  937.     }
  938.     for (var i = 0; i < properties.length; i++)
  939.       aProperties.AppendElement(properties[i]);
  940.   },
  941.  
  942.   getColumnProperties: function(aColumn, aProperties) { },
  943.  
  944.   isContainer: function PTV_isContainer(aRow) {
  945.     this._ensureValidRow(aRow);
  946.  
  947.     var node = this._visibleElements[aRow].node;
  948.     if (PlacesUtils.nodeIsContainer(node)) {
  949.       // the root node is always expandable
  950.       if (!node.parent)
  951.         return true;
  952.  
  953.       // Flat-lists may ignore expandQueries and other query options when
  954.       // they are asked to open a container.
  955.       if (this._flatList)
  956.         return true;
  957.  
  958.       // treat non-expandable childless queries as non-containers
  959.       if (PlacesUtils.nodeIsQuery(node)) {
  960.         var parent = node.parent;
  961.         if((PlacesUtils.nodeIsQuery(parent) ||
  962.             PlacesUtils.nodeIsFolder(parent)) &&
  963.            !node.hasChildren)
  964.           return asQuery(parent).queryOptions.expandQueries;
  965.       }
  966.       return true;
  967.     }
  968.     return false;
  969.   },
  970.  
  971.   isContainerOpen: function PTV_isContainerOpen(aRow) {
  972.     if (this._flatList)
  973.       return false;
  974.  
  975.     this._ensureValidRow(aRow);
  976.     if (!PlacesUtils.nodeIsContainer(this._visibleElements[aRow].node))
  977.       throw Cr.NS_ERROR_INVALID_ARG;
  978.  
  979.     return this._visibleElements[aRow].node.containerOpen;
  980.   },
  981.  
  982.   isContainerEmpty: function PTV_isContainerEmpty(aRow) {
  983.     if (this._flatList)
  984.       return true;
  985.  
  986.     this._ensureValidRow(aRow);
  987.  
  988.     if (!PlacesUtils.nodeIsContainer(this._visibleElements[aRow].node))
  989.       throw Cr.NS_ERROR_INVALID_ARG;
  990.  
  991.     return !this._visibleElements[aRow].node.hasChildren;
  992.   },
  993.  
  994.   isSeparator: function PTV_isSeparator(aRow) {
  995.     this._ensureValidRow(aRow);
  996.     return PlacesUtils.nodeIsSeparator(this._visibleElements[aRow].node);
  997.   },
  998.  
  999.   isSorted: function PTV_isSorted() {
  1000.     return this._result.sortingMode !=
  1001.            Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_NONE;
  1002.   },
  1003.  
  1004.   canDrop: function PTV_canDrop(aRow, aOrientation) {
  1005.     if (!this._result)
  1006.       throw Cr.NS_ERROR_UNEXPECTED;
  1007.  
  1008.     // drop position into a sorted treeview would be wrong
  1009.     if (this.isSorted())
  1010.       return false;
  1011.  
  1012.     var ip = this._getInsertionPoint(aRow, aOrientation);
  1013.     return ip && PlacesControllerDragHelper.canDrop(ip);
  1014.   },
  1015.  
  1016.   _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) {
  1017.     var container = this._result.root;
  1018.     var dropNearItemId = -1;
  1019.     // When there's no selection, assume the container is the container
  1020.     // the view is populated from (i.e. the result's itemId).
  1021.     if (index != -1) {
  1022.       var lastSelected = this.nodeForTreeIndex(index);
  1023.       if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
  1024.         // If the last selected item is an open container, append _into_
  1025.         // it, rather than insert adjacent to it. 
  1026.         container = lastSelected;
  1027.         index = -1;
  1028.       }
  1029.       else if (lastSelected.containerOpen &&
  1030.                orientation == Ci.nsITreeView.DROP_AFTER &&
  1031.                lastSelected.hasChildren) {
  1032.         // If the last selected item is an open container and the user is
  1033.         // trying to drag into it as a first item, really insert into it.
  1034.         container = lastSelected;
  1035.         orientation = Ci.nsITreeView.DROP_ON;
  1036.         index = 0;
  1037.       }
  1038.       else {
  1039.         // Use the last-selected node's container unless the root node
  1040.         // is selected, in which case we use the root node itself as the
  1041.         // insertion point.
  1042.         container = lastSelected.parent || container;
  1043.  
  1044.         // avoid the potentially expensive call to getIndexOfNode() 
  1045.         // if we know this container doesn't allow insertion
  1046.         if (PlacesControllerDragHelper.disallowInsertion(container))
  1047.           return null;
  1048.  
  1049.         var queryOptions = asQuery(this._result.root).queryOptions;
  1050.         if (queryOptions.sortingMode !=
  1051.               Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
  1052.           // If we are within a sorted view, insert at the ends
  1053.           index = -1;
  1054.         }
  1055.         else if (queryOptions.excludeItems ||
  1056.                  queryOptions.excludeQueries ||
  1057.                  queryOptions.excludeReadOnlyFolders) {
  1058.           // Some item may be invisible, insert near last selected one.
  1059.           // We don't replace index here to avoid requests to the db,
  1060.           // instead it will be calculated later by the controller.
  1061.           index = -1;
  1062.           dropNearItemId = lastSelected.itemId;
  1063.         }
  1064.         else {
  1065.           var lsi = PlacesUtils.getIndexOfNode(lastSelected);
  1066.           index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
  1067.         }
  1068.       }
  1069.     }
  1070.  
  1071.     if (PlacesControllerDragHelper.disallowInsertion(container))
  1072.       return null;
  1073.  
  1074.     return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
  1075.                               index, orientation,
  1076.                               PlacesUtils.nodeIsTagQuery(container),
  1077.                               dropNearItemId);
  1078.   },
  1079.  
  1080.   drop: function PTV_drop(aRow, aOrientation) {
  1081.     // We are responsible for translating the |index| and |orientation| 
  1082.     // parameters into a container id and index within the container, 
  1083.     // since this information is specific to the tree view.
  1084.     var ip = this._getInsertionPoint(aRow, aOrientation);
  1085.     if (!ip)
  1086.       return;
  1087.     PlacesControllerDragHelper.onDrop(ip);
  1088.   },
  1089.  
  1090.   getParentIndex: function PTV_getParentIndex(aRow) {
  1091.     this._ensureValidRow(aRow);
  1092.     var parent = this._visibleElements[aRow].node.parent;
  1093.     if (!parent || parent.viewIndex < 0)
  1094.       return -1;
  1095.  
  1096.     return parent.viewIndex;
  1097.   },
  1098.  
  1099.   hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) {
  1100.     this._ensureValidRow(aRow);
  1101.     if (aRow == this._visibleElements.length -1) {
  1102.       // this is the last thing in the list -> no next sibling
  1103.       return false;
  1104.     }
  1105.  
  1106.     var thisLevel = this._visibleElements[aRow].node.indentLevel;
  1107.     for (var i = aAfterIndex + 1; i < this._visibleElements.length; ++i) {
  1108.       var nextLevel = this._visibleElements[i].node.indentLevel;
  1109.       if (nextLevel == thisLevel)
  1110.         return true;
  1111.       if (nextLevel < thisLevel)
  1112.         break;
  1113.     }
  1114.     return false;
  1115.   },
  1116.  
  1117.   getLevel: function PTV_getLevel(aRow) {
  1118.     this._ensureValidRow(aRow);
  1119.  
  1120.     // Level is 0 for items at the root level, 1 for its children and so on.
  1121.     // If we don't show the result's root node, the level is simply the node's
  1122.     // indentLevel; if we do, it is the node's indentLevel increased by 1.
  1123.     // That is because nsNavHistoryResult uses -1 as the indent level for the
  1124.     // root node regardless of our internal showRoot state.
  1125.     if (this._showRoot)
  1126.       return this._visibleElements[aRow].node.indentLevel + 1;
  1127.  
  1128.     return this._visibleElements[aRow].node.indentLevel;
  1129.   },
  1130.  
  1131.   getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
  1132.     this._ensureValidRow(aRow);
  1133.  
  1134.     // only the title column has an image
  1135.     if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
  1136.       return "";
  1137.  
  1138.     var node = this._visibleElements[aRow].node;
  1139.     var icon = node.icon;
  1140.     if (icon)
  1141.       return icon.spec;
  1142.     return "";
  1143.   },
  1144.  
  1145.   getProgressMode: function(aRow, aColumn) { },
  1146.   getCellValue: function(aRow, aColumn) { },
  1147.  
  1148.   getCellText: function PTV_getCellText(aRow, aColumn) {
  1149.     this._ensureValidRow(aRow);
  1150.  
  1151.     var node = this._visibleElements[aRow].node;
  1152.     var columnType = this._getColumnType(aColumn);
  1153.     switch (columnType) {
  1154.       case this.COLUMN_TYPE_TITLE:
  1155.         // normally, this is just the title, but we don't want empty items in
  1156.         // the tree view so return a special string if the title is empty.
  1157.         // Do it here so that callers can still get at the 0 length title
  1158.         // if they go through the "result" API.
  1159.         if (PlacesUtils.nodeIsSeparator(node))
  1160.           return "";
  1161.         return PlacesUIUtils.getBestTitle(node);
  1162.       case this.COLUMN_TYPE_TAGS:
  1163.         return node.tags;
  1164.       case this.COLUMN_TYPE_URI:
  1165.         if (PlacesUtils.nodeIsURI(node))
  1166.           return node.uri;
  1167.         return "";
  1168.       case this.COLUMN_TYPE_DATE:
  1169.         if (node.time == 0 || !PlacesUtils.nodeIsURI(node)) {
  1170.           // hosts and days shouldn't have a value for the date column.
  1171.           // Actually, you could argue this point, but looking at the
  1172.           // results, seeing the most recently visited date is not what
  1173.           // I expect, and gives me no information I know how to use.
  1174.           // Only show this for URI-based items.
  1175.           return "";
  1176.         }
  1177.         if (this._getRowSessionStatus(aRow) != this.SESSION_STATUS_CONTINUE)
  1178.           return this._convertPRTimeToString(node.time);
  1179.         return "";
  1180.       case this.COLUMN_TYPE_VISITCOUNT:
  1181.         return node.accessCount;
  1182.       case this.COLUMN_TYPE_KEYWORD:
  1183.         if (PlacesUtils.nodeIsBookmark(node))
  1184.           return PlacesUtils.bookmarks.getKeywordForBookmark(node.itemId);
  1185.         return "";
  1186.       case this.COLUMN_TYPE_DESCRIPTION:
  1187.         const annos = PlacesUtils.annotations;
  1188.         if (annos.itemHasAnnotation(node.itemId, DESCRIPTION_ANNO))
  1189.           return annos.getItemAnnotation(node.itemId, DESCRIPTION_ANNO)
  1190.         return "";
  1191.       case this.COLUMN_TYPE_DATEADDED:
  1192.         if (node.dateAdded)
  1193.           return this._convertPRTimeToString(node.dateAdded);
  1194.         return "";
  1195.       case this.COLUMN_TYPE_LASTMODIFIED:
  1196.         if (node.lastModified)
  1197.           return this._convertPRTimeToString(node.lastModified);
  1198.         return "";
  1199.     }
  1200.     return "";
  1201.   },
  1202.  
  1203.   setTree: function PTV_setTree(aTree) {
  1204.     var hasOldTree = this._tree != null;
  1205.     this._tree = aTree;
  1206.  
  1207.     // do this before detaching from result when there is no tree.
  1208.     // This ensures that the visible indices of the elements in the
  1209.     // result have been set to -1
  1210.     this._finishInit();
  1211.  
  1212.     if (!aTree && hasOldTree && this._result) {
  1213.       // detach from result when we are detaching from the tree.
  1214.       // This breaks the reference cycle between us and the result.
  1215.       this._result.viewer = null;
  1216.     }
  1217.   },
  1218.  
  1219.   toggleOpenState: function PTV_toggleOpenState(aRow) {
  1220.     if (!this._result)
  1221.       throw Cr.NS_ERROR_UNEXPECTED;
  1222.     this._ensureValidRow(aRow);
  1223.  
  1224.     var node = this._visibleElements[aRow].node;
  1225.     if (!PlacesUtils.nodeIsContainer(node))
  1226.       return; // not a container, nothing to do
  1227.  
  1228.     if (this._flatList && this._openContainerCallback) {
  1229.       this._openContainerCallback(node);
  1230.       return;
  1231.     }
  1232.  
  1233.     var resource = this._getResourceForNode(node);
  1234.     if (resource) {
  1235.       const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
  1236.       const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
  1237.  
  1238.       if (node.containerOpen)
  1239.         PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
  1240.       else
  1241.         PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
  1242.     }
  1243.  
  1244.     node.containerOpen = !node.containerOpen;
  1245.   },
  1246.  
  1247.   cycleHeader: function PTV_cycleHeader(aColumn) {
  1248.     if (!this._result)
  1249.       throw Cr.NS_ERROR_UNEXPECTED;
  1250.  
  1251.     // Sometimes you want a tri-state sorting, and sometimes you don't. This
  1252.     // rule allows tri-state sorting when the root node is a folder. This will
  1253.     // catch the most common cases. When you are looking at folders, you want
  1254.     // the third state to reset the sorting to the natural bookmark order. When
  1255.     // you are looking at history, that third state has no meaning so we try
  1256.     // to disallow it.
  1257.     //
  1258.     // The problem occurs when you have a query that results in bookmark
  1259.     // folders. One example of this is the subscriptions view. In these cases,
  1260.     // this rule doesn't allow you to sort those sub-folders by their natural
  1261.     // order.
  1262.     var allowTriState = PlacesUtils.nodeIsFolder(this._result.root);
  1263.  
  1264.     var oldSort = this._result.sortingMode;
  1265.     var oldSortingAnnotation = this._result.sortingAnnotation;
  1266.     var newSort;
  1267.     var newSortingAnnotation = "";
  1268.     const NHQO = Ci.nsINavHistoryQueryOptions;
  1269.     var columnType = this._getColumnType(aColumn);
  1270.     switch (columnType) {
  1271.       case this.COLUMN_TYPE_TITLE:
  1272.         if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING)
  1273.           newSort = NHQO.SORT_BY_TITLE_DESCENDING;
  1274.         else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING)
  1275.           newSort = NHQO.SORT_BY_NONE;
  1276.         else
  1277.           newSort = NHQO.SORT_BY_TITLE_ASCENDING;
  1278.  
  1279.         break;
  1280.       case this.COLUMN_TYPE_URI:
  1281.         if (oldSort == NHQO.SORT_BY_URI_ASCENDING)
  1282.           newSort = NHQO.SORT_BY_URI_DESCENDING;
  1283.         else if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING)
  1284.           newSort = NHQO.SORT_BY_NONE;
  1285.         else
  1286.           newSort = NHQO.SORT_BY_URI_ASCENDING;
  1287.  
  1288.         break;
  1289.       case this.COLUMN_TYPE_DATE:
  1290.         if (oldSort == NHQO.SORT_BY_DATE_ASCENDING)
  1291.           newSort = NHQO.SORT_BY_DATE_DESCENDING;
  1292.         else if (allowTriState &&
  1293.                  oldSort == NHQO.SORT_BY_DATE_DESCENDING)
  1294.           newSort = NHQO.SORT_BY_NONE;
  1295.         else
  1296.           newSort = NHQO.SORT_BY_DATE_ASCENDING;
  1297.  
  1298.         break;
  1299.       case this.COLUMN_TYPE_VISITCOUNT:
  1300.         // visit count default is unusual because we sort by descending
  1301.         // by default because you are most likely to be looking for
  1302.         // highly visited sites when you click it
  1303.         if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
  1304.           newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
  1305.         else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING)
  1306.           newSort = NHQO.SORT_BY_NONE;
  1307.         else
  1308.           newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
  1309.  
  1310.         break;
  1311.       case this.COLUMN_TYPE_KEYWORD:
  1312.         if (oldSort == NHQO.SORT_BY_KEYWORD_ASCENDING)
  1313.           newSort = NHQO.SORT_BY_KEYWORD_DESCENDING;
  1314.         else if (allowTriState && oldSort == NHQO.SORT_BY_KEYWORD_DESCENDING)
  1315.           newSort = NHQO.SORT_BY_NONE;
  1316.         else
  1317.           newSort = NHQO.SORT_BY_KEYWORD_ASCENDING;
  1318.  
  1319.         break;
  1320.       case this.COLUMN_TYPE_DESCRIPTION:
  1321.         if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING &&
  1322.             oldSortingAnnotation == DESCRIPTION_ANNO) {
  1323.           newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING;
  1324.           newSortingAnnotation = DESCRIPTION_ANNO;
  1325.         }
  1326.         else if (allowTriState &&
  1327.                  oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING &&
  1328.                  oldSortingAnnotation == DESCRIPTION_ANNO)
  1329.           newSort = NHQO.SORT_BY_NONE;
  1330.         else {
  1331.           newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING;
  1332.           newSortingAnnotation = DESCRIPTION_ANNO;
  1333.         }
  1334.  
  1335.         break;
  1336.       case this.COLUMN_TYPE_DATEADDED:
  1337.         if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING)
  1338.           newSort = NHQO.SORT_BY_DATEADDED_DESCENDING;
  1339.         else if (allowTriState &&
  1340.                  oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING)
  1341.           newSort = NHQO.SORT_BY_NONE;
  1342.         else
  1343.           newSort = NHQO.SORT_BY_DATEADDED_ASCENDING;
  1344.  
  1345.         break;
  1346.       case this.COLUMN_TYPE_LASTMODIFIED:
  1347.         if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING)
  1348.           newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING;
  1349.         else if (allowTriState &&
  1350.                  oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING)
  1351.           newSort = NHQO.SORT_BY_NONE;
  1352.         else
  1353.           newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
  1354.  
  1355.         break;
  1356.       case this.COLUMN_TYPE_TAGS:
  1357.         if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING)
  1358.           newSort = NHQO.SORT_BY_TAGS_DESCENDING;
  1359.         else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING)
  1360.           newSort = NHQO.SORT_BY_NONE;
  1361.         else
  1362.           newSort = NHQO.SORT_BY_TAGS_ASCENDING;
  1363.  
  1364.         break;
  1365.       default:
  1366.         throw Cr.NS_ERROR_INVALID_ARG;
  1367.     }
  1368.     this._result.sortingAnnotation = newSortingAnnotation;
  1369.     this._result.sortingMode = newSort;
  1370.   },
  1371.  
  1372.   isEditable: function PTV_isEditable(aRow, aColumn) {
  1373.     // At this point we only support editing the title field.
  1374.     if (aColumn.index != 0)
  1375.       return false;
  1376.  
  1377.     var node = this.nodeForTreeIndex(aRow);
  1378.     if (!PlacesUtils.nodeIsReadOnly(node) &&
  1379.         (PlacesUtils.nodeIsFolder(node) ||
  1380.          (PlacesUtils.nodeIsBookmark(node) &&
  1381.           !PlacesUtils.nodeIsLivemarkItem(node))))
  1382.       return true;
  1383.  
  1384.     return false;
  1385.   },
  1386.  
  1387.   setCellText: function PTV_setCellText(aRow, aColumn, aText) {
  1388.     // we may only get here if the cell is editable
  1389.     var node = this.nodeForTreeIndex(aRow);
  1390.     if (node.title != aText) {
  1391.       var txn = PlacesUIUtils.ptm.editItemTitle(node.itemId, aText);
  1392.       PlacesUIUtils.ptm.doTransaction(txn);
  1393.     }
  1394.   },
  1395.  
  1396.   selectionChanged: function() { },
  1397.   cycleCell: function PTV_cycleCell(aRow, aColumn) { },
  1398.   isSelectable: function(aRow, aColumn) { return false; },
  1399.   performAction: function(aAction) { },
  1400.   performActionOnRow: function(aAction, aRow) { },
  1401.   performActionOnCell: function(aAction, aRow, aColumn) { }
  1402. };
  1403.  
  1404. function PlacesTreeView(aShowRoot, aFlatList, aOnOpenFlatContainer) {
  1405.   if (aShowRoot && aFlatList)
  1406.     throw("Flat-list mode is not supported when show-root is set");
  1407.  
  1408.   this._tree = null;
  1409.   this._result = null;
  1410.   this._showSessions = false;
  1411.   this._selection = null;
  1412.   this._visibleElements = [];
  1413.   this._showRoot = aShowRoot;
  1414.   this._flatList = aFlatList;
  1415.   this._openContainerCallback = aOnOpenFlatContainer;
  1416. }
  1417.